home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 68K / Lib / img / imgconvert.py < prev    next >
Text File  |  1996-05-20  |  16KB  |  542 lines

  1. """Module to handle conversion between in-core image formats."""
  2. #
  3. # Image-conversion helper classes and routines
  4. #
  5. import imgformat
  6. import imgcolormap
  7. import imageop
  8. import imgop
  9.  
  10. error = 'imgconvert.error'
  11. unsupported_error = 'imgconvert.unsupported_error'
  12.  
  13. HIGH_QUALITY=1
  14. TRACE=0
  15.  
  16. import os
  17. if os.environ.has_key('FASTDITHER'):
  18.     HIGH_QUALITY=0
  19.  
  20. def setquality(onoff):
  21.     """Call with zero parameter to disable high-quality conversions"""
  22.     
  23.     global HIGH_QUALITY
  24.  
  25.     old_q = HIGH_QUALITY
  26.     HIGH_QUALITY = onoff
  27.     return old_q
  28.  
  29. def settrace(onoff):
  30.     """Call with non-zero parameter to enable conversion trace printing"""
  31.     
  32.     global TRACE
  33.  
  34.     old_t = TRACE
  35.     TRACE = onoff
  36.     return old_t
  37.  
  38. _colormaps = {}
  39.  
  40. #
  41. # getfmtcolormap - Create a colormap for an 8-bit format
  42. def getfmtcolormap(fmt):
  43.         """Return a colormap suitable for the 8-bit format argument"""
  44.     
  45.     if _colormaps.has_key(fmt):
  46.         return _colormaps[fmt]
  47.         
  48.     try:
  49.         size = fmt.descr['size']
  50.     except AttributeError:
  51.         raise error, 'Argument must be an image-format object'
  52.     if size <> 8:
  53.         raise error, 'Only 8-bit formats supported'
  54.     comp = fmt.descr['comp']
  55.     if len(comp) not in (1, 3):
  56.         raise error, 'Only 1- or 3-component formats supported'
  57.         
  58.     map = imgcolormap.new('\0\0\0\0'*256)
  59.     if len(comp) == 1:
  60.         for i in range(1<<comp[0][1]):
  61.             map[i] = (i, i, i)
  62.     else:
  63.         rmax = 1<<comp[0][1]
  64.         gmax = 1<<comp[1][1]
  65.         bmax = 1<<comp[2][1]
  66.         for r in range(rmax):
  67.             rv = r*255/(rmax-1)
  68.             for g in range(gmax):
  69.                 gv = g*255/(gmax-1)
  70.                 for b in range(bmax):
  71.                     bv = b*255/(bmax-1)
  72.                     i = (r<<comp[0][0]) | (g<<comp[1][0]) | (b<<comp[2][0])
  73.                     map[i] = (rv, gv, bv)
  74.     _colormaps[fmt] = map
  75.     return map    
  76.  
  77. #
  78. # _reverse - Convert top-to-bottom to bottom-to-top vv.
  79. #
  80. def _reverse(data, reader, srcfmt, dstfmt):
  81.     if srcfmt.descr['size'] <> dstfmt.descr['size'] or \
  82.        srcfmt.descr['align'] <> dstfmt.descr['align']:
  83.     raise error, 'Incompatible formats'
  84.     width = reader.width
  85.     # convert width-in-pixels to width-in-bytes, taking alignment into account
  86.     align = srcfmt.descr['align'] / 8 - 1
  87.     width = (width * srcfmt.descr['size'] / 8 + align) & ~align
  88.     height = len(data) / width
  89.     if height*width <> len(data):
  90.     raise error, 'Incorrect datasize'
  91.     rv = ''
  92.     pos = len(data)-width
  93.     while pos >= 0:
  94.     rv = rv + data[pos:pos+width]
  95.     pos = pos - width
  96.     return rv
  97.  
  98. #
  99. # _maptorgb - Convert colormap to rgb
  100. #
  101. def _maptorgb(data, reader, srcfmt, dstfmt):
  102.     width = reader.width
  103.     return reader.colormap.map(data, width, srcfmt, dstfmt)
  104.  
  105. #
  106. # _grey2rgb - Convert greyscale to rgb
  107. #
  108. def _greytorgb(data, reader, srcfmt, dstfmt):
  109.     greymap = getfmtcolormap(srcfmt)
  110.     # We have to trick map() in believing us...
  111.     if srcfmt == imgformat.grey:
  112.     srcfmt = imgformat.colormap
  113.     elif srcfmt == imgformat.xgrey:
  114.     srcfmt = imgformat.xcolormap
  115.     elif srcfmt == imgformat.grey_b2t:
  116.     srcfmt = imgformat.grey_b2t
  117.     return greymap.map(data, reader.width, srcfmt, dstfmt)
  118.  
  119. #
  120. # _rgb2grey - Convert rgb to greyscale
  121. #
  122. def _rgbtoxgrey(data, reader, srcfmt, dstfmt):
  123.     data = imageop.rgb2grey(data, reader.width, reader.height)
  124.     return data
  125.  
  126. #
  127. # _rgb2rgb8 - Convert rgb to xrgb8, simplistic method
  128. #
  129. #def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  130. #    return imageop.rgb2rgb8(data, reader.width, reader.height)
  131.  
  132.     
  133. #
  134. # _shuffle - Convert various RGB formats to each other
  135. #
  136. def _shuffle(data, reader, srcfmt, dstfmt):
  137.     return imgop.shuffle(data, reader.width, reader.height, srcfmt, dstfmt)
  138.  
  139. #
  140. # _dither - Convert 8-bit grey to 1-bit grey
  141. def _dither(data, reader, srcfmt, dstfmt):
  142.     return imgop.dither(data, reader.width, reader.height, srcfmt, dstfmt)
  143.     
  144. #
  145. # _zapbits - Remove bits from an RGB color (for colormap clustering)
  146. #
  147. class Struct: pass
  148.  
  149. def _zapbits(data, reader, fmt):
  150.     # Create a new format, initially identical to the old one
  151.     newfmt = Struct()
  152.     newfmt.name = 'Scaled-down RGB format for map-clustering'
  153.     newfmt.descr = {}
  154.     for k in fmt.descr.keys():
  155.     newfmt.descr[k] = fmt.descr[k]
  156.     # Now remove one bit from each color component
  157.     components = newfmt.descr['comp']
  158.     newcomp = ()
  159.     for pos, len in components:
  160.     if len <= 1:
  161.         raise error, 'Map-clustering failed'
  162.     newcomp = newcomp + ((pos+1, len-1),)
  163.     if TRACE:
  164.     print '    Scaling RGB values to', newcomp
  165.     newfmt.descr['comp'] = newcomp
  166.     data = imgop.shuffle(data, reader.width, reader.height, fmt, newfmt)
  167.     return data, newfmt
  168.  
  169. def _scalergbdata(data, reader, srcfmt):
  170.     try:
  171.     map = imgcolormap.fromimage(data, reader.width, reader.height, srcfmt)
  172.     return data, map
  173.     except imgcolormap.error, arg:
  174.     if arg[:15] != 'Too many colors':
  175.         raise imgcolormap.error, arg
  176.     # Do color-clustering
  177.     newfmt = srcfmt
  178.     while 1:
  179.     data, newfmt = _zapbits(data, reader, newfmt)
  180.     data = imgop.shuffle(data, reader.width, reader.height,
  181.                  newfmt, srcfmt)
  182.     try:
  183.         map = imgcolormap.fromimage(data, reader.width, reader.height,
  184.                     srcfmt)
  185.         return data, map
  186.     except imgcolormap.error:
  187.         pass
  188.  
  189. #
  190. # _maprgb - Convert RGB to xcolormap format
  191. #
  192. def _maprgb(data, reader, srcfmt, dstfmt):
  193.     data, map = _scalergbdata(data, reader, srcfmt)
  194.     reader.colormap = map
  195.     data, newfmt = map.dither(data, reader.width, reader.height,
  196.                   srcfmt, HIGH_QUALITY)
  197.     if newfmt != imgformat.xcolormap:
  198.     raise error, 'Internal error: map.dither returned format '+`newfmt`
  199.     return data
  200.     
  201. def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  202.     map = getfmtcolormap(dstfmt)
  203.     data, newfmt = map.dither(data, reader.width, reader.height,
  204.                   srcfmt, HIGH_QUALITY)
  205.     if newfmt != imgformat.xcolormap:
  206.     raise error, 'Internal error: map.dither returned format '+`newfmt`
  207.     return data
  208.     
  209. #
  210. # _removestride - Remove the stride from an 8-bit image
  211. #
  212. def _removestride(data, reader, srcfmt, dstfmt):
  213.     rv = ''
  214.     dstwidth = reader.width
  215.     srcwidth = ((dstwidth+3) & ~3)
  216.     if srcwidth == dstwidth:
  217.     return data
  218.     for i in range(0, len(data), srcwidth):
  219.     rv = rv + data[i:i+dstwidth]
  220.     return rv
  221.  
  222. #
  223. # _addstride - Add stride to an 8-bit image
  224. #
  225. def _addstride(data, reader, srcfmt, dstfmt):
  226.     rv = ''
  227.     srcwidth = reader.width
  228.     dstwidth = ((srcwidth+3) & ~3)
  229.     if srcwidth == dstwidth:
  230.     return data
  231.     extra = '\0' * (dstwidth-srcwidth)
  232.     for i in range(0, len(data), srcwidth):
  233.     rv = rv + data[i:i+srcwidth] + extra
  234.     return rv
  235.  
  236. #
  237. # 'lossiness' is a scalar value. Use 0 if nothing changes in the pixels,
  238. # 1 if no information is lost but bits are (grey->rgb), 2 if information
  239. # is lost (rgb->grey), 3 if the converter also produces a colormap.
  240. #
  241. _converters = [ \
  242.     (imgformat.grey,    imgformat.grey_b2t,    _reverse,    0),
  243.     (imgformat.grey_b2t,imgformat.grey,        _reverse,    0),
  244.     (imgformat.xgrey,    imgformat.xgrey_b2t,    _reverse,    0),
  245.     (imgformat.xgrey_b2t,imgformat.xgrey,    _reverse,    0),
  246.     (imgformat.colormap,imgformat.colormap_b2t,    _reverse,    0),
  247.     (imgformat.colormap_b2t,imgformat.colormap,    _reverse,    0),
  248.     (imgformat.rgb,    imgformat.rgb_b2t,    _reverse,    0),
  249.     (imgformat.rgb_b2t,    imgformat.rgb,        _reverse,    0),
  250.     (imgformat.rgb8,    imgformat.rgb8_b2t,    _reverse,    0),
  251.     (imgformat.rgb8_b2t,imgformat.rgb8,        _reverse,    0),
  252.     (imgformat.xrgb8,    imgformat.xrgb8_b2t,    _reverse,    0),
  253.     (imgformat.xrgb8_b2t,imgformat.xrgb8,    _reverse,    0),
  254.  
  255.     (imgformat.grey,    imgformat.rgb,        _greytorgb,    1),
  256.     (imgformat.xgrey,    imgformat.rgb,        _greytorgb,    1),
  257.     (imgformat.grey_b2t,imgformat.rgb_b2t,    _greytorgb,    1),
  258.  
  259.     (imgformat.colormap,imgformat.rgb,        _maptorgb,    1),
  260.     (imgformat.xcolormap,imgformat.rgb,        _maptorgb,    1),
  261.     (imgformat.colormap_b2t,imgformat.rgb_b2t,    _maptorgb,    1),
  262.  
  263.     (imgformat.rgb,    imgformat.xgrey,    _rgbtoxgrey,    2),
  264. #    (imgformat.rgb,    imgformat.xrgb8,    _rgbtoxrgb8,    2),
  265.  
  266.     (imgformat.macrgb,    imgformat.rgb,        _shuffle,    0),
  267.     (imgformat.macrgb16,imgformat.rgb,        _shuffle,    1),
  268.     (imgformat.rgb8,    imgformat.rgb,        _shuffle,    1),
  269.     (imgformat.xrgb8,    imgformat.rgb,        _shuffle,    1),
  270.     (imgformat.rgb,    imgformat.macrgb,    _shuffle,    0),
  271.     (imgformat.macrgb16,imgformat.macrgb,    _shuffle,    1),
  272.     (imgformat.rgb8,    imgformat.macrgb,    _shuffle,    1),
  273.     (imgformat.xrgb8,    imgformat.macrgb,    _shuffle,    1),
  274.     (imgformat.rgb,    imgformat.macrgb16,    _shuffle,    2),
  275.     (imgformat.macrgb,    imgformat.macrgb16,    _shuffle,    2),
  276.     (imgformat.rgb8,    imgformat.macrgb16,    _shuffle,    1),
  277.     (imgformat.xrgb8,    imgformat.macrgb16,    _shuffle,    1),
  278.  
  279.     (imgformat.rgb,    imgformat.xrgb8,    _rgbtoxrgb8,    2),
  280.  
  281.     (imgformat.xrgb8,    imgformat.rgb8,        _shuffle,    0),
  282.     (imgformat.rgb8,    imgformat.xrgb8,    _shuffle,    0),
  283.  
  284.     (imgformat.pbmbitmap, imgformat.grey,    _shuffle,    1),
  285.     (imgformat.grey,    imgformat.pbmbitmap,    _dither,    2),
  286.  
  287.     (imgformat.grey,    imgformat.xgrey,    _removestride,    0),
  288.     (imgformat.grey_b2t,imgformat.xgrey_b2t,    _removestride,    0),
  289.     (imgformat.rgb8_b2t,imgformat.xrgb8_b2t,    _removestride,    0),
  290.     (imgformat.colormap,imgformat.xcolormap,    _removestride,    0),
  291.     (imgformat.xgrey,    imgformat.grey,        _addstride,    0),
  292.     (imgformat.xgrey_b2t,imgformat.grey_b2t,    _addstride,    0),
  293.     (imgformat.xrgb8_b2t,imgformat.rgb8_b2t,    _addstride,    0),
  294.     (imgformat.xcolormap,imgformat.colormap,    _addstride,    0),
  295.  
  296.     (imgformat.rgb,    imgformat.xcolormap,    _maprgb,    3),
  297. ]
  298.  
  299. #
  300. # The converts we have built, indexed by sourceformat.
  301. # Each entry is another dictionary (indexed by dstformat).
  302. # The entries of these dictionaries are lists of [lossiness, len, [funcs]]
  303. #
  304. _generated = {}
  305.  
  306. #
  307. # Add a converter from 'srcfmt' to 'dstfmt' to the list, possibly
  308. # replacing an existing converter
  309. #
  310. def addconverter(srcfmt, dstfmt, func, lossy):
  311.         """Tell imgconvert about a new converter.
  312.     Args: source_format, dest_format, function, lossy.
  313.     lossy is 0 (not lossy), 1 (wastes bits), 2 (loses bits) or
  314.     3 (converts to colormap format).
  315.  
  316.     function is called as function(data, reader, srcfmt, dstfmt)
  317.     """
  318.     
  319.     for i in range(len(_converters)):
  320.         isrcfmt, idstfmt, irtn, ilossy = _converters[i]
  321.         if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  322.             _converters[i] = (srcfmt, dstfmt, func, lossy)
  323.             return
  324.     _converters.append((srcfmt,dstfmt,func,lossy))
  325.  
  326. #
  327. # Returns a list of conversion functions that will convert
  328. # srcfmt to dstfmt if applied in that order.
  329. #
  330. def getconverter(srcfmt, dstfmt):
  331.         """Return a converter from srcfmt to dstfmt.
  332.     A converter is a list [lossy, length, list-of-tuples],
  333.     where each tuple is (srcfmt, dstfmt, func, lossy).
  334.     Calling each of the functions in order will convert your image.
  335.     """
  336.     
  337.         global _generated
  338.     #
  339.     # If formats are the same return the dummy converter
  340.     #
  341.     if srcfmt == dstfmt: return []
  342.     #
  343.     # Otherwise, if we have a converter, return that one
  344.     #
  345.     for this in _converters:
  346.             isrcfmt, idstfmt, irtn, ilossy = this
  347.         if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  348.             return [ilossy, 1, [this]]
  349.     #
  350.     # Finally, we try to create a converter
  351.     #
  352.     if not _generated.has_key(srcfmt):
  353.             # Not there yet. Try to create it.
  354.         _generated[srcfmt] = _enumerate_converters(srcfmt)
  355.         
  356.     if not _generated[srcfmt].has_key(dstfmt):
  357.         raise unsupported_error, (srcfmt, dstfmt)
  358.  
  359.     cf = _generated[srcfmt][dstfmt]
  360.     return cf
  361.  
  362. def _enumerate_converters(srcfmt):
  363.     cvs = {}
  364.     formats = [srcfmt]
  365.     steps = 0
  366.     while 1:
  367.         workdone = 0
  368.         for this in _converters:
  369.                 isrcfmt, idstfmt, irtn, ilossy = this
  370.                 #
  371.             # First see if the source format is of any use.
  372.             #
  373.             if isrcfmt == srcfmt:
  374.                     #
  375.                 # This converter directly understands our
  376.                 # source format. Remember it.
  377.                 #
  378.                 template = [ilossy, 1, [this]]
  379.             elif cvs.has_key(isrcfmt):
  380.                     #
  381.                 # We have a path to this format, so
  382.                 # this converter can help us further.
  383.                 #
  384.                 template = cvs[isrcfmt][:]
  385.                 template[0] = max(template[0], ilossy)
  386.                 template[1] = template[1] + 1
  387.                 template[2] = template[2] + [this]
  388.             else:
  389.                 continue
  390.             #
  391.             # Next, check whether we want this converter
  392.             # (if it is the first one for this dstfmt, or
  393.             # if it is better than what we have)
  394.             #
  395.             if not cvs.has_key(idstfmt):
  396.                 cvs[idstfmt] = template
  397.                 workdone = 1
  398.             else:
  399.                 previous = cvs[idstfmt]
  400.                 if template < previous:
  401.                     cvs[idstfmt] = template
  402.                     workdone = 1
  403.         if not workdone:
  404.             break
  405.         #
  406.         # Finally, a check for loops.
  407.         #
  408.         steps = steps + 1
  409.         if steps > len(_converters):
  410.             print '------------------loop in emunerate_converters--------'
  411.             print 'CONVERTERS:'
  412.             print _converters
  413.             print 'RESULTS:'
  414.             print cvs
  415.             raise error, 'Internal error - loop'
  416.     return cvs
  417.  
  418. def stackreader(dstfmt, reader):
  419.     """Create a reader-like object that reads image file data and
  420.     converts it to the requested format.
  421.     Args: format, original_reader
  422.     """
  423.     
  424.     if dstfmt in reader.format_choices:
  425.     reader.format = dstfmt
  426.     return reader
  427.     # Nope, not supported directly. Find all possible converters
  428.     list = []
  429.     for srcfmt in reader.format_choices:
  430.     try:
  431.         rv = getconverter(srcfmt, dstfmt)
  432.     except unsupported_error:
  433.         continue
  434.     if rv:
  435.         [lossy, len, funclist] = rv
  436.         list.append(lossy, len, srcfmt, funclist)
  437.     if not list:
  438.     raise unsupported_error, (reader.format_choices, dstfmt)
  439.     # Now, sort and use the best
  440.     list.sort()
  441.     lossy, len, srcfmt, funclist = list[0]
  442.     if lossy == 3:
  443.     return _MapReaderStack(reader, dstfmt, srcfmt, funclist)
  444.     else:
  445.     return _ConverterStack(reader, dstfmt, srcfmt, funclist)
  446.  
  447. def stackwriter(srcfmt, writer):
  448.     """Create a writer-like object that writes an image file from source
  449.     data in the specified format.
  450.     Args: source_format, destination_writer
  451.     """
  452.     
  453.     if srcfmt in writer.format_choices:
  454.     writer.format = srcfmt
  455.     return writer
  456.     # Nope, not supported directly. Find all possible converters
  457.     list = []
  458.     for dstfmt in writer.format_choices:
  459.     try:
  460.         [lossy, len, funclist] = getconverter(srcfmt, dstfmt)
  461.     except unsupported_error:
  462.         continue
  463.     list.append(lossy, len, dstfmt, funclist)
  464.     if not list:
  465.     raise unsupported_error, (srcfmt, writer.format_choices)
  466.     # Now, sort and use the best
  467.     list.sort()
  468.     lossy, len, dstfmt, funclist = list[0]
  469.     return _ConverterStack(writer, srcfmt, dstfmt, funclist)
  470.  
  471. #
  472. # The placeholder class
  473. class _ConverterStack:
  474.     def __init__(self, base, ourfmt, basefmt, funclist):
  475.     self._base = base
  476.     self._funclist = funclist
  477.     self._copyattrtoself()
  478.     self.format_choices = (ourfmt,)
  479.     self.format = ourfmt
  480.     self._basefmt = basefmt
  481.  
  482.     def _copyattrtoself(self):
  483.     srcdict = self._base.__dict__
  484.     dstdict = self.__dict__
  485.     for k in srcdict.keys():
  486.         if k[0] <> '_':
  487.         dstdict[k] = srcdict[k]
  488.  
  489.     def _copyattrfromself(self):
  490.     srcdict = self.__dict__
  491.     dstdict = self._base.__dict__
  492.     for k in srcdict.keys():
  493.         if k[0] <> '_':
  494.         dstdict[k] = srcdict[k]
  495.  
  496.     def read(self):
  497.     self._copyattrfromself()
  498.     self._base.format = self._basefmt
  499.     data = self._base.read()
  500.     if TRACE:
  501.         print 'Converting', self._basefmt.name, 'to', self.format.name,
  502.         print 'in', len(self._funclist), 'steps:'
  503.     for f in self._funclist:
  504.         if TRACE:
  505.         print '  ',f[0].name, 'to',f[1].name
  506.         data = apply(f[2], (data, self, f[0], f[1]))
  507.     return data
  508.  
  509.     def write(self, data):
  510.     if TRACE:
  511.         print 'Converting', self.format.name, 'to', self._basefmt.name,
  512.         print 'in', len(self._funclist), 'steps:'
  513.     for f in self._funclist:
  514.         if TRACE:
  515.         print '  ',f[0].name, 'to',f[1].name
  516.         data = apply(f[2], (data, self, f[0], f[1]))
  517.     self._copyattrfromself()
  518.     self._base.format = self._basefmt
  519.     self._base.write(data)
  520.  
  521. #
  522. # A MapReaderStack is used if one of the converters also returns a
  523. # colormap. In this case we have to read upon init to set the colormap
  524. # attribute.
  525. #
  526. class _MapReaderStack(_ConverterStack):
  527.     def __init__(self, base, ourfmt, basefmt, funclist):
  528.     _ConverterStack.__init__(self, base, ourfmt, basefmt, funclist)
  529.     self._base.format = self._basefmt
  530.     data = self._base.read()
  531.     if TRACE:
  532.         print 'Converting', self._basefmt.name, 'to', self.format.name,
  533.         print 'in', len(self._funclist), 'steps:'
  534.     for f in self._funclist:
  535.         if TRACE:
  536.         print '  ',f[0].name, 'to',f[1].name
  537.         data = apply(f[2], (data, self, f[0], f[1]))
  538.     self._data = data
  539.  
  540.     def read(self):
  541.     return self._data
  542.